Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signal in rsutils #12228

Merged
merged 13 commits into from
Oct 1, 2023
Merged

Signal in rsutils #12228

merged 13 commits into from
Oct 1, 2023

Conversation

maloel
Copy link
Contributor

@maloel maloel commented Sep 27, 2023

  • Moved out of types.h to rsutils/signal.h
  • Fixed bug with && fowarding (actually need to NOT use std::forward!)
  • Fixed bug with deferred::detach()
  • Separated public_signal usecase from base signal class
  • Removed operators (+=, -=, ())
  • Added unit-test
  • Removed usage in some cases that have only a single callback
  • API is now add() and remove() when manually dealing with slots, and just subscribe() when dealing with subscription objects that automatically clean up
  • The only dangerous commit is the last: a subscription will now be removed when the rs2_device is removed, and overriding playback-status-changed callbacks will actually remove the previous

Tracked on [LRS-890]

@maloel maloel requested a review from OhadMeir September 27, 2023 12:05
fix test-signal stress time measurement

// Returns the deferred function so you can move it somewhere else
fn && detach() { return std::move( _deferred ); }
fn detach() { return std::move( _deferred ); }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about the state of _deferred after the move. Acording to https://stackoverflow.com/questions/13680587/move-semantic-with-stdfunction it will be in an unspecified state, that might not be empty.
If subscription is detached and then canceled it might call the detached funciton?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It says "Unless otherwise specified" -- meaning if a move constructor or operator was not defined. In the case of std::function, it is defined. It is true that, up to C++20, it is not guaranteed to be valid. But this is tested in the unit-test. And between us, we know it resets to nothing...

void _Reset_move(_Func_class&& _Right) noexcept { // move _Right's stored object
        if (!_Right._Empty()) {
            if (_Right._Local()) { // move and tidy
                _Set(_Right._Getimpl()->_Move(&_Mystorage));
                _Right._Tidy();
            } else { // steal from _Right
                _Set(_Right._Getimpl());
                _Right._Set(nullptr);
            }
        }
    }
function(function&& _Right) noexcept {
        this->_Reset_move(_STD move(_Right));
    }

Anyway, if this is a problem then it's a problem in deferred which was there before too. Let's talk?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If subscription is detached and then canceled it might call the detached funciton?

A detach will do _d.detach(), meaning _d should now be nothing. If you then cancel() then _d is nothing and nothing should happen. Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll make sure this is covered in the unit-test.

add detach() followed by cancel()
//
// Commonly used with signal.
//
class subscription
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a "notify" method, enabling calling the deffered function not only on destruction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what cancel() is... right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cancel() is one time only because it detaches the function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want to call it multiple times?
Then you can detach and store it elsewhere. The whole point of a subscription is that the unsubscribe action happens once and only once...

subscription_slot add( callback && func )
{
std::lock_guard< std::mutex > locker( _impl->mutex );
// NOTE: we should maintain ordering of subscribers: later subscriptions should be called after earlier ones, so
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to keep order of calls? We should not rely on it when using a general utility.
We might not be able to guarantee this if moving to other implementations, e.g. boost.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's general-purpose is exactly why I wanted to keep the order.
If the order is non-deterministic, then the function (result of using all the callbacks) isn't deterministic. E.g., the unit-test actually has a callback that gets an std::string & argument -- it actually changes the output value. The caller decides the functionality of the callbacks, and therefore losing determinism is not a good thing IMO. It doesn't cost much, either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might not be able to guarantee this if moving to other implementations, e.g. boost.

That's true. I actually implemented using vector<>. It was faster to create and run the signals. And you're right, it would have been harder to make it deterministic, though possible. The problem was the complexity. In the end I chose to go back to a map<> implementation because of its straightforwardness. It's good enough for our needs.

We're not likely to change the implementation. And if we do, it's better if we ensure determinism then, too.


// Unlike add(), subscribing implies a subscription, meaning the caller needs to store the return value or else it
// will get unsubscribed immediately!!
subscription subscribe( callback && func )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you subscribe callbacks that receive arguments? subscription is using defered::fn which does not receive any

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the beauty of it: a subscription doesn't have to know anything about what it's unsubscribing. It doesn't even have to work with signal -- you can have anything return a subscription.

The "callback" that goes into a subscription is always without arguments -- it has to be called from a destructor. if you want to pass stuff in, you do it thru the lambda, like signal does it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I get it now.
Maybe the name subscription is not the best, as it is not like a subscription to a publisher. It is only a one time call to a function, suitable to be used as a deleter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, a subscription is just a deferred function but with different semantics: one main difference is the operator=() calling cancel().

Think of it like a subscription in English, not a subscription in the context of publisher-subscriber.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not too sure about what are the benefits of using a subscription over a deferred function though

Copy link
Contributor Author

@maloel maloel Sep 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semantics.
Deferred will not cancel() on assignment.
Deferred has no cancel().
deferred is a good utility to have. subscription is a use-case with its own set of requirements..

list->dev,
&list->dev.device->get_sensor(index)
list->device,
&list->device->get_sensor(index)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that list->device is a shared_ptr and not an object shouldn't we test that is is not empty before dereferencing?
Same for other places in the file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a shared_ptr before, too -- just contained inside list->dev.device

Copy link
Contributor

@OhadMeir OhadMeir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@maloel maloel merged commit aff5337 into IntelRealSense:development Oct 1, 2023
@maloel maloel deleted the signal branch October 1, 2023 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants